#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <vector>

#include <unistd.h>    // sleep(1);
#include <sys/types.h> //
#include <sys/wait.h>

// using namespace std;

#define MakeSeed() srand((unsigned long)time(nullptr) ^ getpid())

#define PROCESS_NUM 5

//////////////////////////////// 子进程要完成的某一种任务 -- 模拟一下///////
typedef void (*func_t)();

void downLoadTask()
{
    std::cout << getpid() << ": 下载任务\n"
              << std::endl;
    sleep(1);
}

void ioTask()
{
    std::cout << getpid() << ": IO任务\n"
              << std::endl;
    sleep(1);
}

void flushTask()
{
    std::cout << getpid() << ": 刷新任务\n"
              << std::endl;
    sleep(1);
}

void loadTaskFunc(std::vector<func_t> *funcMap)
{
    assert(funcMap != nullptr);

    // 函数名就表示的是函数地址，不用再取地址了
    funcMap->push_back(downLoadTask);
    funcMap->push_back(ioTask);
    funcMap->push_back(flushTask);
}

////////////////////////////////       下面的代码一个多线程程序              ///////
class subEp
{
public:
    subEp(pid_t subId, int writeFd)
        : subId_(subId), writeFd_(writeFd)
    {
        // 单独对 name_ 初始化
        char nameBuffer[1024];
        snprintf(nameBuffer, sizeof nameBuffer, "process-%d[pid(%d)-fd(%d)]", num++, subId_, writeFd_);
        name_ = nameBuffer;
    }

    // private:
    static int num;
    std::string name_;
    pid_t subId_; // 下面的一个函数需要访问这个成员，所以设置成为 public 类型的
    int writeFd_;
};

int subEp::num = 0; // static 成员得在 class 外面初始化的哦

int recvTask(int readFd)
{
    int code = 0;
    ssize_t s = read(readFd, &code, sizeof code);
    if (s == sizeof code)
        return code;
    else if (s <= 0) // 下面的是接收不正常的
        return -1;
    else
        return 0;
}

void createSubProcess(std::vector<subEp> *subs, std::vector<func_t> &funcMap)
{
    for (int i = 0; i < PROCESS_NUM; i++)
    {
        int fds[2];
        int n = pipe(fds);
        assert(n == 0);
        (void)n;

        // 父进程打开的文件，是会被子进程共享的
        pid_t id = fork();
        if (id == 0)
        {
            // 子进程，进行读取，处理任务
            close(fds[1]);
            while (true)
            {
                // 1. 获取命令码，如果没有发送，我们的子进程应该阻塞
                int commandCode = recvTask(fds[0]);
                // 2. 完成任务
                if (commandCode >= 0 && commandCode < funcMap.size())
                    funcMap[commandCode]();
                else if (commandCode == -1)
                    break;
            }
            exit(0);
        }
        close(fds[0]);
        subEp sub(id, fds[1]); // claee subEp 的构造函数需要的就是 pid_t 和 writeFd_
        subs->push_back(sub);
    }
}

void sendTask(const subEp &process, int taskNum)
{
    std::cout << "send task num: " << taskNum << " send to -> " << process.name_ << std::endl;
    int n = write(process.writeFd_, &taskNum, sizeof taskNum);
    // assert(n == sizeof int); error
    assert(n == sizeof(int));
    (void)n;
}

void loadBlanceContrl(std::vector<subEp> &subs, std::vector<func_t> &funcMap, int count)
{
    int processnum = subs.size();
    int tasknum = funcMap.size();
    bool forever = (count == 0) ? true : false;

    while (true)
    {
        // 1. 选择一个子进程 --> std::vector<subEp> -> index - 随机数
        int subIdx = rand() % processnum;
        // 2. 选择一个任务 --> std::vector<func_t> -> index
        int taskIdx = rand() % tasknum;
        // 3. 任务发送给选择的进程
        sendTask(subs[subIdx], taskIdx);
        sleep(1);
        if (forever == false)
        {
            count--;
            if (count == 0)
                break;
        }
    }
    // write quit --> read 0
    for (int i = 0; i < PROCESS_NUM; i++)
        close(subs[i].writeFd_);
}

void waitProcess(std::vector<subEp> &processes)
{
    int processnum = processes.size();
    for (int i = 0; i < processnum; i++)
    {
        waitpid(processes[i].subId_, nullptr, 0);
        std::cout << "wait sub process sucess ...: " << processes[i].subId_ << std::endl;
    }
}

int main(void)
{
    MakeSeed();
    // 1. 建立子进程并建立和子进程通信的通道，有bug,但是步影响我们后面的编写
    // [子进程id, wfd]
    // 1.1 加载方案表
    std::vector<func_t> funcMap;
    loadTaskFunc(&funcMap);
    // 1.2 创建子进程，并且维护好父子通信信道
    std::vector<subEp> subs;
    createSubProcess(&subs, funcMap);

    // 2. 走到这里就是父进程，控制子进程,负载均衡的向子进程发送命令码
    int taskCnt = 3; // taskCnt == 0 的时候不断地发送命令
    loadBlanceContrl(subs, funcMap, taskCnt);

    // 3. 回收子进程信息
    waitProcess(subs);

    return 0;
}
